package com.mapforge.map;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import com.mapforge.selection.ComparablePoint;
import com.mapforge.selection.Selection;
import com.mapforge.selection.SetOperationType;

/**
 * The {@link ByteBitmask} class uses a 2D 8-bit byte matrix to determine which
 * cells are valid for operations. Specifically, integer's bitwise And'd with
 * values contained within the underlying cells are considered valid for
 * operations.
 * 
 * @see Mask
 * @author Will Morrison
 * 
 */
public class ByteBitmask implements Mask {
	private byte[][] bitmask;
	private boolean empty;

	/**
	 * Constructs a new {@link ByteBitmask} of the specified width and height,
	 * initialized with all cells containing 1. (So odd integers at all
	 * <code>(x,y)</code> coordinates passed to
	 * {@link #checkMask(int, int, int)} would return true. )
	 * 
	 * @param width
	 *            specified width of 2D matrix
	 * @param height
	 *            specified height of 2D matrix
	 */
	public ByteBitmask(int width, int height) {
		this(width, height, (byte) 1);
	}

	/**
	 * Constructs a new {@link ByteBitmask} of the specified width and height,
	 * initialized with all cells having the specified initial value.
	 * 
	 * @param width
	 *            specified width of 2D matrix
	 * @param height
	 *            specified height of 2D matrix
	 * @param initialVal
	 *            value all cells within matrix contain initially
	 * @throws IllegalArgumentException
	 *             if specified width or height are less than 1
	 */
	public ByteBitmask(int width, int height, byte initialVal)
			throws IllegalArgumentException {
		if (width < 1 || height < 1) {
			throw new IllegalArgumentException(
					"Mask must have positive nonzero dimensions");
		}
		this.bitmask = new byte[height][width];
		// since all bytes are initially 0...
		if (initialVal != 0) {
			for (int i = 0; i < height; i++) {
				Arrays.fill(bitmask[i], initialVal);
			}
		}
		empty = true;
	}

	/**
	 * Clears this {@link ByteBitmask} by setting all values in the matrix to 0.
	 */
	public void clearMask() {
		clearMask((byte) 0);
	}

	/**
	 * Clears this {@link ByteBitmask} by setting all values in the byte matrix
	 * to the specified value.
	 * 
	 * @param value
	 *            specified value
	 */
	public void clearMask(byte value) {
		if (!empty) {
			for (int i = 0; i < bitmask[0].length; i++)
				Arrays.fill(bitmask[i], value);
		}
	}

	/**
	 * Returns true if the specified bit value results in a one when bitwise
	 * and'd with the value stored in the specific location.
	 * 
	 * @param x
	 *            specified x location of value to perform bitwise and with
	 * @param y
	 *            specified y location of cell value to perform bitwise and with
	 * @param bitval
	 *            specified value to perform bitwise and against a value in this
	 *            bitmask
	 * @return whether a bitwise operation against a specific cell in this
	 *         bitmask results in a value other than 0.
	 */
	public boolean checkMask(int x, int y, long value)
			throws ArrayIndexOutOfBoundsException {
		return (bitmask[y][x] & value) != 0;
	}

	@Override
	public void setValues(SetOperationType type, Selection selection, long value) {
		Iterator<ComparablePoint> it = selection.iterator();
		ComparablePoint p;
		switch (type) {
		case NEW:
			this.clearMask();
			while (it.hasNext()) {
				p = it.next();
				// truncate to byte
				bitmask[p.y][p.x] = (byte) value;
			}
			empty = false;
			break;
		case ADDITION:
			while (it.hasNext()) {
				p = it.next();
				// truncate to byte
				bitmask[p.y][p.x] = (byte) value;
			}
			empty = false;
			break;
		case SUBTRACTION:
			while (it.hasNext()) {
				p = it.next();
				if (checkMask(p.x, p.y, value)) {
					// truncate to byte
					bitmask[p.y][p.x] = (byte) 0;
				}
			}
			break;
		case INTERSECTION:
			List<ComparablePoint> list = new ArrayList<ComparablePoint>();
			while (it.hasNext()) {
				p = it.next();
				if (checkMask(p.x, p.y, value)) {
					list.add(p);
				}
			}
			clearMask();
			empty = list.isEmpty();
			if (!empty) {
				it = list.iterator();
				while (it.hasNext()) {
					p = it.next();
					bitmask[p.y][p.x] = (byte) 1;
				}
			}
			break;
		}
	}

	@Override
	public int getHeight() {
		return bitmask.length;
	}

	@Override
	public int getWidth() {
		return bitmask[0].length;
	}
}